
#ifndef LOD_LIGHTING_H
#define LOD_LIGHTING_H

#include "../types.h"
#include "../gl_function_wrappers.h"
#include "../base/point4.h"
#include "light.h"

#include <vector>
#include <assert.h>

#include <stdlib.h>
#include <GL/glew.h>
//#include <GL/gl.h>


// todo
#include <iostream>
using std::cout;
using std::endl;

class lighting
{

   public:
   lighting(int lights_number)
    : lights(lights_number),
      is_light_set(lights_number, false)
   {
      assert(lights_number >= 0); // although no lights case at first 
                                // seems to be senseless, you can still manage
                                //  lighting options, which is quite useful 
      init_object();
   }


   lighting()
    : lights(MAX_LIGHTS),
      is_light_set(MAX_LIGHTS, false)
   {
      init_object();
   }


   /*!
    * To get the lighting actually working, call update() followed by enable() */
   void set_glob_amb(const comp_type* ga)
   {
       glob_amb[0] = ga[0];
       glob_amb[1] = ga[1];
       glob_amb[2] = ga[2];
       glob_amb[3] = ga[3];

       glLightModelfv(GL_LIGHT_MODEL_AMBIENT, glob_amb);
   }

   void set_smooth_shading()
   {
       shade_model = GL_SMOOTH;
       glShadeModel(GL_SMOOTH);
   }

   void set_flat_shading()
   {
       shade_model = GL_FLAT;
       glShadeModel(GL_FLAT);
   }

   void swap_shading()
   {
       if(shade_model==GL_FLAT)
           set_smooth_shading();
       else
           set_flat_shading();
   }

   bool is_smooth_shading()
   {
       return shade_model==GL_SMOOTH;
   }

   bool is_flat_shading()
   {
       return shade_model==GL_FLAT;
   }

   bool enable() // returns the previous state
   {
       bool b = glIsEnabled(GL_LIGHTING);
       glEnable(GL_LIGHTING);
       return b;
   }

   bool disable()   // returns the previous state
   {
       bool b = glIsEnabled(GL_LIGHTING);
       glDisable(GL_LIGHTING);
       return b;
   }

   void disable_all()
   {
       for(int i=0; i<MAX_LIGHTS; i++)
           glDisable(GL_LIGHT0+i);
   }

   void swap()
   {
       if(glIsEnabled(GL_LIGHTING))
           disable();
       else
           enable();
   }

   /*!
    * Since the place where the light will be put depends on the current 
    * transformation matrix, you have to take there is a correct matrix
    * in the moment this method is called. */
   void update(bool only_pos=false)
   {
      int i, end;
      //disable();
      //todo
      //for(i=0; i<MAX_LIGHTS; i++) 
      //   glDisable(GL_LIGHT0 + i); // turn off all the lights
      end = lights.size();
      for(i=0; i<end; i++) {
          if(is_light_set[i]) {
              GLLIGHTV(GL_LIGHT0 + i, GL_POSITION, lights[i].pos);
              if(only_pos==false) {
                  GLLIGHTV(GL_LIGHT0 + i, GL_AMBIENT, lights[i].amb);
                  GLLIGHTV(GL_LIGHT0 + i, GL_DIFFUSE, lights[i].diff);
                  GLLIGHTV(GL_LIGHT0 + i, GL_SPECULAR, lights[i].spec);
                  glEnable(GL_LIGHT0 + i);
              }
          }
          else {
              if(only_pos==false)       // it is assumed that if just the position
                  glDisable(GL_LIGHT0 + i); // is being updated, then everything
                                            // for every light has already been set up
          }
      }

      if(only_pos==false) {
          GLLIGHTMODELV(GL_LIGHT_MODEL_AMBIENT, glob_amb);
          glShadeModel(shade_model);
      }
      //enable();
   }

   /*!
    * Call update() after this method to actually apply changes. 
    * \return Identifier of the newly added light (-1 if the light wasn't created)
    * */
   int add_light(const comp_type* ambient, const comp_type* diffuse, 
                  const comp_type* specular, const co_type* position)
   {
       int i, end;
       end = lights.size();
       for(i=0; i<end; i++) {
           if(is_light_set[i]==false) 
           {
               if(ambient!=NULL && diffuse!=NULL && specular!=NULL)
                   lights[i] = light(ambient,diffuse,specular,position);
               else
                   lights[i] = light(position);

               is_light_set[i] = true;
               break;
           }
       }
       if(i<end)
           return i;
       else
           return -1;
   }


   /*! You have to call update() for the changes in lighting to actually apply. */
   int add_light(const comp_type* position)
   {
       return add_light(NULL, NULL, NULL, position);
   }

   /*!
    * Call update() after this method to actually apply changes. */
   void del_all_lights()
   {
       int i, end;
       end = lights.size();
       for(i=0; i<end; i++)
           is_light_set[i] = false;
   }

   std::vector<point4<co_type> > get_lights_pos()
   {
       std::vector<point4<co_type> >  p;
       int end = lights.size();
       for(int i=0; i<end; i++) 
          if(is_light_set[i]) {
              p.push_back(point4<co_type>( lights[i].pos ));
          } 
       return p;
   }

   point4<co_type> get_light_pos(int light_id)
   {
       assert(light_id >= 0  &&  light_id < (int)lights.size());
       return point4<co_type>( lights[light_id].pos );
   }

   //todo
   //void change_pos(point4<co_type> old_pos, point4<co_type> new_pos)
       /*int i, end;
       end = lights.size();
       for(i=0; i<end; i++) {
           if(is_light_set[i] && lights[i].pos[0]==old_pos.x
                           && lights[i].pos[1]==old_pos.y
                           && lights[i].pos[2]==old_pos.z
                           && lights[i].pos[3]==old_pos.w)
           {
               lights[i].pos[0] = new_pos.x;
               lights[i].pos[1] = new_pos.y;
               lights[i].pos[2] = new_pos.z;
               lights[i].pos[3] = new_pos.w;
           }
       }*/

   //! Changes the position of the specified light
   void change_pos(int light_id, point4<co_type> new_pos)
   {
       int end = lights.size();
       assert(light_id >= 0 && light_id < end);
       lights[light_id].pos[0] = new_pos.x;
       lights[light_id].pos[1] = new_pos.y;
       lights[light_id].pos[2] = new_pos.z;
       lights[light_id].pos[3] = new_pos.w;
   }

   void change_pos(int light_id, point3<co_type> new_pos)
   {
       change_pos(light_id, point4<co_type>(new_pos.x, new_pos.y, new_pos.z, 
                                            0)); // directional light
   }


   private:
   void init_object()
   {
      glob_amb[0] = 0.2;
      glob_amb[1] = 0.2;
      glob_amb[2] = 0.2;    // OpenGL defaults
      glob_amb[3] = 1.0;

      shade_model = GL_SMOOTH;
   }

   comp_type glob_amb[4];
   GLenum shade_model;
   std::vector<light> lights;
   std::vector<bool> is_light_set;

   public:
   static const int MAX_LIGHTS = 4;
};

#endif // LOD_LIGHTING_H
